/* === LICENSE INFO ===
 * This source file is subject to the GPLv3 license that is bundled with this
 * package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://www.gnu.org/licenses/gpl.txt
 * === end of LICENSE INFO === */

/*==============================================================================
  File Name   : GLog.cpp
  Description : Definition for GLog module of gUtil.

  Created on  : 2009-07-26
  Updated on  : 2011-01-30
  Updated by  : Lee Shiou Ming

  Cautions    : (important notes)
  ============================================================================*/

//------------------------------------------------------------------------------
//-- INCLUDES & DEFINES
//------------------------------------------------------------------------------
#include "GLog.hpp"

#include <iostream>
#include <stdexcept>

using namespace gUtil::GLog;


//------------------------------------------------------------------------------
//-- INITIALIZATIONS
//------------------------------------------------------------------------------
boost::scoped_ptr<GLog> GLog::sm_uniqueInstance(new GLog());
boost::recursive_mutex GLog::sm_configMutex;
boost::mutex GLog::sm_loggerDeletionMutex;


//------------------------------------------------------------------------------
//-- FUNCTION IMPLEMENTATIONS
//------------------------------------------------------------------------------
GLog::GLog()
{
}


bool GLog::setMaxLogLevel(const LogLevel _maxLogLevel)
{
    boost::recursive_mutex::scoped_lock lock(sm_configMutex);

    validateObjectAccess();
    bool returnValue = false;

    if (!sm_uniqueInstance->m_logConfig.m_isConfigured)
    {
        sm_uniqueInstance->m_logConfig.m_maxLogLevel = _maxLogLevel;
        returnValue = true;
    }

    return returnValue;
}


bool GLog::addOutputType(const LogOutputType _enabledOutput)
{
    boost::recursive_mutex::scoped_lock lock(sm_configMutex);

    validateObjectAccess();
    bool returnValue = false;

    if (!sm_uniqueInstance->m_logConfig.m_isConfigured)
    {
        sm_uniqueInstance->m_logConfig.m_enabledOutputTypes.insert(_enabledOutput);
        returnValue = true;
    }

    return returnValue;
}


bool GLog::setLogFilePath(const std::string& _logFilePath)
{
    boost::recursive_mutex::scoped_lock lock(sm_configMutex);

    validateObjectAccess();
    bool returnValue = false;

    if (!sm_uniqueInstance->m_logConfig.m_isConfigured)
    {
        sm_uniqueInstance->m_logConfig.m_logFilePath = _logFilePath;
        returnValue = true;
    }

    return returnValue;
}


bool GLog::setLogRotationSize(unsigned int _logRotationSize)
{
    boost::recursive_mutex::scoped_lock lock(sm_configMutex);

    validateObjectAccess();
    bool returnValue = false;

    if (!sm_uniqueInstance->m_logConfig.m_isConfigured)
    {
        if (_logRotationSize < 1)
        {
            sm_uniqueInstance->m_logConfig.m_logRotationSize =
                    DEFAULT_LOG_ROTATION_SIZE_BYTE;
        }
        else if (_logRotationSize > MAX_LOG_FILE_SIZE_BYTE)
        {
            sm_uniqueInstance->m_logConfig.m_logRotationSize =
                    MAX_LOG_FILE_SIZE_BYTE;
        }
        else
        {
            sm_uniqueInstance->m_logConfig.m_logRotationSize = _logRotationSize;
        }

        returnValue = true;
    }

    return returnValue;
}


bool GLog::setLogRotationInterval(const TimeInterval _logRotationInterval)
{
    boost::recursive_mutex::scoped_lock lock(sm_configMutex);

    validateObjectAccess();
    bool returnValue = false;

    if (!sm_uniqueInstance->m_logConfig.m_isConfigured)
    {
        sm_uniqueInstance->m_logConfig.m_logRotationInterval = _logRotationInterval;
        returnValue = true;
    }

    return returnValue;
}


bool GLog::initConfig()
{
    boost::recursive_mutex::scoped_lock lock(sm_configMutex);

    validateObjectAccess();
    bool returnValue = false;

    if (!sm_uniqueInstance->m_logConfig.m_isConfigured)
    {
        sm_uniqueInstance->initLogWriters();
        sm_uniqueInstance->m_logConfig.m_isConfigured = true;
        returnValue = true;
    }

    return returnValue;
}


bool GLog::loadFromConfigFile(const std::string& _configFile,
        const ConfigFileFormat _configFileFormat)
{
    boost::recursive_mutex::scoped_lock lock(sm_configMutex);

    validateObjectAccess();
    bool returnValue = false;

    if (!sm_uniqueInstance->m_logConfig.m_isConfigured)
    {
        returnValue = sm_uniqueInstance->m_logConfig.loadFromFile(_configFile,
                _configFileFormat);

        if (returnValue)
        {
            sm_uniqueInstance->initLogWriters();
            sm_uniqueInstance->m_logConfig.m_isConfigured = true;
        }
    }
    else
    {
        std::cerr << "[ERROR] " << __PRETTY_FUNCTION__ <<
                ": Logger is already configured. " <<
                "No changes will be apply to logger." << std::endl;
    }

    return returnValue;
}


bool GLog::getConfigState()
{
    boost::recursive_mutex::scoped_lock lock(sm_configMutex);

    validateObjectAccess();
    return sm_uniqueInstance->m_logConfig.m_isConfigured;
}


std::string GLog::getLogFilePath()
{
    boost::recursive_mutex::scoped_lock lock(sm_configMutex);

    validateObjectAccess();
    return sm_uniqueInstance->m_logConfig.m_logFilePath;
}


gUtil::GLog::LogLevel GLog::getMaxLogLevel()
{
    boost::recursive_mutex::scoped_lock lock(sm_configMutex);

    validateObjectAccess();
    return sm_uniqueInstance->m_logConfig.m_maxLogLevel;
}


bool GLog::checkOutputEnabledState(const LogOutputType _outputType)
{
    boost::recursive_mutex::scoped_lock lock(sm_configMutex);

    validateObjectAccess();
    bool returnValue = false;

    std::set<LogOutputType>::iterator outputTypeItr =
            sm_uniqueInstance->m_logConfig.m_enabledOutputTypes.find(_outputType);
    std::set<LogOutputType>::iterator notFoundItr =
            sm_uniqueInstance->m_logConfig.m_enabledOutputTypes.end();

    if (outputTypeItr != notFoundItr)
    {
        // Specified output type is in the list of enabled output types.
        returnValue = true;
    }

    return returnValue;
}


GLog& GLog::getInstance()
{
    validateObjectAccess();

    if (!sm_uniqueInstance->m_logConfig.m_isConfigured)
    {
        std::cout << "[NOTICE] Logger is not configured. " <<
            "Will use default setting." << std::endl;

        sm_uniqueInstance->initDefaultConfig();
    }

    return *sm_uniqueInstance;
}


bool GLog::append(const std::string _logMessage, const LogLevel _logLevel)
{
    bool returnValue = false;

    try
    {
        validateObjectAccess();

        if (filterByLogLevel(_logLevel))
        {
            m_writerManager.appendLog(_logMessage,
                    LOG_LEVEL_VALUE_MAP_TABLE[_logLevel].mappedValue);
            returnValue = true;
        }
    }
    catch (std::exception& e)
    {
        std::cerr << "[ERROR] GLog::append(): " << e.what() << std::endl;
    }
    catch (...)
    {
        std::cerr << "[ERROR] GLog::append(): Unknown exception detected." <<
                std::endl;
    }

    return returnValue;
}


inline void GLog::validateObjectAccess()
{
    if (NULL == sm_uniqueInstance)
    {
        throw std::runtime_error("Invalid reference to GLog instance.");
    }
}


void GLog::initDefaultConfig()
{
    boost::recursive_mutex::scoped_lock lock(sm_configMutex);

    if (!m_logConfig.m_isConfigured)
    {
        m_logConfig.m_maxLogLevel = DEFAULT_MAX_LOG_LEVEL;

        m_logConfig.m_enabledOutputTypes.clear();
        m_logConfig.m_enabledOutputTypes.insert(DEFAULT_LOG_OUTPUT_TYPE);

        m_logConfig.m_logFilePath = DEFAULT_LOG_FILE_PATH;

        initLogWriters();

        m_logConfig.m_isConfigured = true;
    }
}


bool GLog::initLogWriters()
{
    boost::recursive_mutex::scoped_lock lock(sm_configMutex);

    bool returnValue = true;

    m_writerManager.setLogFilePath(m_logConfig.m_logFilePath);
    m_writerManager.setLogRotationSize(m_logConfig.m_logRotationSize);
    m_writerManager.setLogRotationInterval(m_logConfig.m_logRotationInterval);
    if (m_logConfig.m_enabledOutputTypes.empty())
    {
        std::cout << "[NOTICE] No log output is enabled. " <<
            "Will use default output: " << DEFAULT_LOG_OUTPUT_TYPE << std::endl;

        m_logConfig.m_enabledOutputTypes.insert(DEFAULT_LOG_OUTPUT_TYPE);
        m_writerManager.createLogWriter(DEFAULT_LOG_OUTPUT_TYPE);
        returnValue = false;
    }
    else
    {
        std::set<LogOutputType>::iterator outputTypeItr;
        for (outputTypeItr = m_logConfig.m_enabledOutputTypes.begin();
            outputTypeItr != m_logConfig.m_enabledOutputTypes.end();
            ++outputTypeItr)
        {
            m_writerManager.createLogWriter(*outputTypeItr);
        }
    }

    // Comment out next line of code if don't want to use asynchronous logging.
    // i.e. in a single-threaded program.
    m_writerManager.startAsyncLogging();

    return returnValue;
}


bool GLog::filterByLogLevel(const LogLevel _logLevel)
{
    bool returnValue = true;

    if (_logLevel > m_logConfig.m_maxLogLevel)
    {
        returnValue = false;
    }

    return returnValue;
}
